%{ open Ast %}

//      (       )      {     }        [       ]      ;      ,
%token LPAREN RPAREN LBRACE RBRACE LSBRACE RSBRACE  SEMI COMMA 
//        ++         --
%token PLUSPLUS MINUSMINUS
//     == != <  <= >  >=
%token EQ NE LT LE GT GE
//      && ||  !  .
%token AND OR NOT DOT
//      +     -     *      /    %
%token PLUS MINUS TIMES DIVIDE MOD
//       =      +=              -=          *=         %=                   
%token ASSIGN PLUS_ASSIGN MINUS_ASSIGN TIMES_ASSIGN DIVIDE_ASSIGN MOD_ASSIGN
//reserved keywords for control flow
%token RETURN IF ELSE FOR BREAK
//reserved keywords for types
%token INT DOUBLE STRING LIST STRUCT POINT SHAPE
%token <string> ID
//constants
%token <int> CONST_INT
%token <float> CONST_DBL
%token <string> CONST_STR

%token EOF

%nonassoc NOELSE
%nonassoc IF ELSE RETURN FOR BREAK

%right ASSIGN PLUS_ASSIGN MINUS_ASSIGN TIMES_ASSIGN DIVIDE_ASSIGN MOD_ASSIGN COMMA
%left OR
%left AND
%left EQ NE
%left LT GT LE GE
%left PLUS MINUS
%left TIMES DIVIDE MOD
%right NOT
%left PLUSPLUS MINUSMINUS 
%left DOT LPAREN RPAREN LSBRACE RSBRACE

%start program
%type <Ast.program> program

%%

program: 
	  function_definition_list					{ { decl_list = [];
	  								    func_def_list = List.rev $1; } }
	| declaration_list function_definition_list			{ { decl_list = List.rev $1;
	  		   						    func_def_list = List.rev $2; } }

expr:
	  ID LPAREN expr_list_opt RPAREN				{ Expr_func($1, $3) }
	| constant							{ Expr_const($1) }
	| lvalue							{ Expr_lval($1) }  
	| MINUS expr %prec NOT						{ Expr_op("-", $2) }
	| NOT expr							{ Expr_op("!", $2) }
	| lvalue PLUSPLUS						{ Expr_lval_op($1, "++") }
	| lvalue MINUSMINUS						{ Expr_lval_op($1, "--") }
	| expr TIMES expr						{ Expr_binop($1, "*", $3) }
	| expr DIVIDE expr						{ Expr_binop($1, "/", $3) }
	| expr MOD expr							{ Expr_binop($1, "%", $3) }
	| expr PLUS expr						{ Expr_binop($1, "+", $3) }
	| expr MINUS expr						{ Expr_binop($1, "-", $3) }
	| expr LT expr							{ Expr_binop($1, "<", $3) }
	| expr GT expr							{ Expr_binop($1, ">", $3) }
	| expr LE expr							{ Expr_binop($1, "<=", $3) }
	| expr GE expr							{ Expr_binop($1, ">=", $3) }
	| expr EQ expr							{ Expr_binop($1, "==", $3) }
	| expr NE expr							{ Expr_binop($1, "!=", $3) }
	| expr AND expr							{ Expr_binop($1, "&&", $3) }
	| expr OR expr							{ Expr_binop($1, "||", $3) }  
	| lvalue ASSIGN	expr						{ Expr_asgop($1, "=", $3) }
	| lvalue PLUS_ASSIGN expr					{ Expr_asgop($1, "+=", $3) }	
	| lvalue MINUS_ASSIGN expr					{ Expr_asgop($1, "-=", $3) }	
	| lvalue TIMES_ASSIGN expr					{ Expr_asgop($1, "*=", $3) }	
	| lvalue DIVIDE_ASSIGN expr					{ Expr_asgop($1, "/=", $3) }	
	| lvalue MOD_ASSIGN expr					{ Expr_asgop($1, "%=", $3) }	
	| LPAREN expr RPAREN						{ Expr_paren($2) }

lvalue:
	  ID								{ Lval_id($1) }
	| lvalue LSBRACE expr RSBRACE					{ Lval_arr($1, $3) }
	| lvalue DOT ID	       	    					{ Lval_struct($1, $3) }

expr_list_opt:
                                                                        { [] }
        | expr_list     						{ List.rev $1 }

expr_list:
          expr								{ [$1] }
        | expr_list COMMA expr          				{ $3 :: $1 }

stmt_list:
	  stmt  	      						{ [$1] }
	| stmt_list stmt						{ $2 :: $1 }

stmt:
   	  expr SEMI							{ Stmt($1) }
	| LBRACE stmt_list RBRACE					{ Stmt_block(List.rev $2) }
	| IF LPAREN expr RPAREN stmt %prec NOELSE			{ Stmt_if($3, $5) }
	| IF LPAREN expr RPAREN stmt ELSE stmt		  	     	{ Stmt_ifelse($3, $5, $7) }
	| FOR LPAREN expr_opt SEMI expr_opt SEMI expr_opt RPAREN stmt	{ Stmt_for($3, $5, $7, $9) }
	| BREAK SEMI							{ Stmt_break }
	| RETURN expr SEMI						{ Stmt_ret($2) }
	| SEMI	      							{ Stmt_noop }

expr_opt:
	  /* nothing */							{ Expr_opt(None) }
	| expr 	   							{ Expr_opt(Some($1)) }
	
declaration_list:
	  declaration							{ [$1] }
	| declaration_list declaration					{ $2 :: $1 }

declaration:
	  type_specifier declarator_list SEMI				{ Decl($1, List.rev $2) }
        | STRUCT ID LBRACE struct_decl_list RBRACE SEMI			{ Decl_struct($2, List.rev $4) }
        | STRUCT POINT LBRACE struct_decl_list RBRACE SEMI		{ Decl_struct("point", List.rev $4) }
        | STRUCT SHAPE LBRACE struct_decl_list RBRACE SEMI		{ Decl_struct("shape", List.rev $4) }

struct_decl_list:
	  struct_decl							{ [$1] }
	| struct_decl_list struct_decl					{ $2 :: $1 }

struct_decl:
	  type_specifier ID SEMI					{ ($2, $1) }

type_specifier:
	  INT								{ Int }
	| DOUBLE							{ Double }
	| STRING							{ String }
	| LIST LT type_specifier GT %prec DOT				{ List($3) }
	| STRUCT ID  	   		    				{ Struct($2) }
	| POINT	 							{ Struct("point") }
	| SHAPE								{ Struct("shape") }
		
declarator_list:
	  declarator							{ [$1] }
	| declarator_list COMMA declarator				{ $3 :: $1 }

declarator:
	  ID								{ Declarator($1, None) }
	| ID initializer_						{ Declarator($1, Some($2)) }

initializer_:
	  ASSIGN constant_expr						{ Init_const_expr($2) }
	| ASSIGN LBRACE constant_expr_list RBRACE			{ Init_const_expr_list(List.rev $3) }

constant:
	  CONST_INT							{ Const_int($1) }
	| CONST_DBL							{ Const_dbl($1) }
	| CONST_STR							{ Const_str($1) }

constant_expr:
	  constant							{ Const_expr($1) }
	| MINUS constant %prec NOT					{ Const_expr_neg($2) }

constant_expr_list:
	  constant_expr							{ [$1] }
	| constant_expr_list COMMA constant_expr			{ $3 :: $1 }

function_definition:
	  type_specifier function_declarator function_body		{ { type_spec = $1;
	  		 		     				    func_decl = $2;
									    func_body = $3; } }
function_definition_list:
	  function_definition						{ [$1] }
        | function_definition_list function_definition			{ $2 :: $1 }

function_declarator:
	  ID LPAREN param_list_opt RPAREN				{ $1, List.rev $3 }

param_list_opt:
	  /* nothing */							{ [] }
	| parameter_list						{ $1 }

parameter_list:
	  function_parameter						{ [$1] }
	| parameter_list COMMA function_parameter			{ $3 :: $1 }

function_parameter:
	  type_specifier ID						{ $2, $1 }

function_body:
	  LBRACE declaration_list_opt stmt_list RBRACE			{ { func_decl_list = List.rev $2;
	  	 		      					    stmt_list = List.rev $3; } }

declaration_list_opt:		      					
	  /* nothing */							{ [] }
	| declaration_list						{ $1 }
